#include<iostream>
#include<cuda_runtime.h>
#include <time.h>
#include <chrono>

#define CHECK(call)\
do {                                                                         \
    const cudaError_t error = call;                                          \
    if (error != cudaSuccess)                                                \
    {                                                                        \
        fprintf(stderr, "CUDA Error: %s:%d - %s\n",                          \
                __FILE__, __LINE__, cudaGetErrorString(error));             \
        exit(EXIT_FAILURE);                                                  \
    }                                                                        \
} while (0)

void init(float *ip, int size)
{
    for(int i = 0; i < size; i++)
    {
        ip[i] = (float)i;
    }
}
void printMatirx(float *C, const int nx, const int ny)
{
    float *ic = C;
    std::cout<<nx <<" "<<ny<<std::endl;
    for(int i = 0; i < ny; i++)
    {
        for(int p = 0;p < nx; p++)
        {
            std::cout<<ic[p];
        }
        ic += nx;
        std::cout<<std::endl;
    }
    std::cout<<std::endl;
}

bool ok(float*target, float*now, int n)
{
    for(int i = 0; i < n; i++)
        if(target[i] != now[i])
            return 0;
    return 1;
}

void cpuSum(float* a, float *b, float *c, int nx, int ny)
{
    int idx = 0;
    for(int p = 0; p < ny; p++)
    {
        for(int i = 0; i < nx; i++)
        {
            c[idx] = a[idx] + b[idx];
            idx++;
        }
    }
}
__global__ void matrixSum(float* a, float *b, float *c, int nx, int ny)
{
    /*
    这次的例子中一个线程块有4*2个线程，2*3个线程块
    看起来这里的y是行坐标
    */

    int y = 
        blockIdx.y * blockDim.y //比如我是y为1，则y维度前面正好有一组块。一组块在y维度的长度是blockDim.y。
        + threadIdx.y;
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int idx = y * nx + x;
    c[idx] = a[idx] + b[idx];

}

int main()
{
    int dev = 0;
    cudaDeviceProp deviceProp;

    CHECK(cudaGetDeviceProperties(&deviceProp, dev));
    printf("Using Device %d:%s", dev, deviceProp.name);
    CHECK(cudaSetDevice(dev));

    int nx = 1<<14;
    int ny = 1<<14;
    int nxy = nx * ny;
    int nBytes = nxy * sizeof(float);


    float *h_a = new float[nxy];
    float *h_b = new float[nxy];
    float *h_c = new float[nxy];
    float *h_c2 = new float[nxy];
    init(h_a, nxy);
    init(h_b, nxy);
    //printMatirx(h_a, nx, ny);

    float *d_a;
    float *d_b;
    float *d_c;
    float *d_c2;
    cudaMalloc((void **)&d_a, nBytes); //这里void**纯粹是因为这个函数就是这么定义的，没有别的意思
    cudaMalloc((void **)&d_b, nBytes); 
    cudaMalloc((void **)&d_c, nBytes); 
    cudaMalloc((void **)&d_c2, nBytes); 

    cudaMemcpy(d_a, h_a, nBytes, cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, h_b, nBytes, cudaMemcpyHostToDevice);

    dim3 block(128, 128); //每个线程块是4*2个线程
    dim3 grid(
        (nx + block.x - 1) / block.x,
        (ny + block.y - 1) /block.y
    );
    /*
    一般来看，只需要8/4就知道网格中的x维度需要2个线程块。但是如果
    不能整除，则要多于2个。比如x为9则需要3个线程块。
    （9 + 4 - 1）/4 = 3。总之就是个找规律的问题。
    */
    //来矩阵求和
    auto astart = std::chrono::high_resolution_clock::now();
    cpuSum(h_a, h_b, h_c, nx, ny);
    auto aend = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> duration = aend - astart;
    printf("代码执行耗时: %f \n",duration);


    cudaEvent_t t0, t1; //cuda的话用这个几时
    cudaEventCreate(&t0);
    cudaEventCreate(&t1);
    cudaEventRecord(t0);
    
    matrixSum<<<grid, block>>>(d_a, d_b, d_c2, nx, ny);

    cudaEventRecord(t1);//告诉 GPU 在执行完所有之前的操作后，标记一个时间点 t1。这个操作本身是异步的，它只是将一个“记录事件”的命令放入 CUDA 流中。CPU 不会等待这个命令真正执行。
    cudaEventSynchronize(t1); // 这是一个同步操作。它会阻塞 CPU，直到 GPU 真正执行到流中 t1 所在的位置，并且 t1 之前的所有操作都已完成。只有当 t1 真正“发生”了，你才能准确地测量它与 t0 之间的时间差。。
    float ms = 0;
    cudaEventElapsedTime(&ms, t0, t1);

    std::cout << "代码执行耗时: " << ms  << " 秒" << std::endl;


    cudaMemcpy(h_c2, d_c2, nBytes, cudaMemcpyDeviceToHost);
    std::cout << "是否一样 "<< ok(h_c, h_c2, nxy) << std::endl;



    //这个习惯还是要有的。
    cudaFree(d_a);
    cudaFree(d_b);
    delete h_a;
    delete h_b;
    cudaDeviceReset();
    return 0;

}










